package weeny.pre.util;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.data.Edge;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Schema;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.expression.AndPredicate;
import prefuse.data.expression.Expression;
import prefuse.data.expression.Function;
import prefuse.data.expression.FunctionTable;
import prefuse.data.expression.NotPredicate;
import prefuse.data.expression.Predicate;
import prefuse.data.tuple.TupleSet;
import prefuse.data.util.ColumnProjection;
import prefuse.data.util.NamedColumnProjection;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.render.NullRenderer;
import prefuse.render.PolygonRenderer;
import prefuse.render.Renderer;
import prefuse.render.RendererFactory;
import prefuse.render.ShapeRenderer;
import prefuse.util.DataLib;
import prefuse.util.FontLib;
import prefuse.util.PredicateChain;
import prefuse.util.PrefuseLib;
import prefuse.util.StrokeLib;
import prefuse.visual.EdgeItem;
import prefuse.visual.NodeItem;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;
import prefuse.visual.VisualTable;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.sort.ItemSorter;
import weeny.face.WidgetTabbedPane;
import weeny.pre.display.MoreVisualization;
import weeny.pre.expression.NullPredicate;
import weeny.pre.event.DisplayListener;
import weeny.pre.face.ExpressionBox;
import weeny.pre.face.FilterBox;
import weeny.pre.face.GraphLoadSaveBox;
import weeny.pre.face.PreJTable;
import weeny.pre.face.RendererBox;
import weeny.pre.face.operation.GroupOperation;
import weeny.pre.face.operation.OperationBox;
import weeny.pre.io.MyGraphReader;
import weeny.pre.renderer.PolylineRenderer;
import weeny.util.io.IOUtil;
import weeny.util.io.ModUtil;
import weeny.util.logs.Debug;

import static prefuse.render.PolygonRenderer.POLYGON;
import static prefuse.visual.VisualItem.*;

/**
 * Marker is just a wrapper of VisualGraph which integrates visual items,
 * renderers, and itemsorters. You also can load or save everything in this
 * marker.
 * 
 * 1) There is a {@link #dGraph} in it, the load or save operation is
 *    dealing with this member variable. By default, there are some commom
 *    fields {@link #dataEdgeSchema} and {@link #dataNodeSchema}
 *    already there. But you can call {@link #addDataField(String, Class)}
 *    to add more if you want them to be saved. However, if you do not need them
 *    to be saved, you can call {@link #addVisualField(String, Class)}
 *    instead, so the added columns will not be saved. But be careful what types
 *    of columns can be saved to file or parsed from strings. We cannot save all
 *    kinds of objects, for more details, please see {@link MyGraphReader}.
 *    You also can load any graph you like, for example, graphs saved by some
 *    other applications. Then we will check the input to see if the data has
 *    enough visual attribtues to make them probably rendered. If not, we show
 *    a dialog to ask you to setup them probably. More details can be found in
 *    {@link #createLoadSaveBox(String)}
 * 2) The renderers are handled by marker itself. There are two PredicateChains
 *    in the marker, the default one called {@link #basicChain}, the user
 *    defined one called {@link #customizedChain}. First we lookup the
 *    item in customizedChain if we can find a renderer, that is it. Otherwise, 
 *    lookup the item in basicChain, if we find a renderer that is it, but if we
 *    still cannot find a renderer, use {@link #dummyRenderer}. So the
 *    next question will be what is the rule to lookup renderer for each item?
 *    We use focus groups. We use {@link InGroupPredicate} to test each item, if
 *    it is true, then we use the Renderer assigned to the Predicate. See
 *    {@link #addRenderer(String, Renderer)}, you will understand. There
 *    are already six default renderers and six groups defined in the default
 *    chain. Since one tuple can be added to multiple focus groups, if a tuple
 *    is added to several focus groups, and each focus group has a renderer to
 *    go with it, which focus group should be picked:
 *    a) The one in {@link #customizedChain} is prefered.
 *    b) The earlier-added one is prefered, if several in the same chain.
 * 3) We sort items in marker by a interger column called {@link #DEPTH}.
 *    change the value in the field of a tuple, you will change its z-depth in
 *    the renderer process. When two items have the same value, their order will
 *    be decided as:
 *    a) "edge before node"
 *    b) "first added first rendering"
 * 4) We have a load/save interface for the data stored in this marker.
 *
 * 
 * All the data tuples in marker need to be added to some focus renderer groups
 * to be rendered. To use them, we have three ways to add data items
 * 1) dummy functions: adding dummy node with no focus group, no shape, no
 *    color, no nothing
 * 2) mark functions: adding a visual item having visual attributes, but without
 *    any focus groups
 * 3) draw functions: based on the purposes of drawing, adding a visual item
 *    with proper visual attributes, and proper basic focus group  
 * 
 * 
 * 
 * Some important fields used in graph tables:
 *          usually set to true when mark shape/polygon/curve
 * TEXT:    used to determine the text content to display when mark string only
 *          exists in node table
 * DEPTH:   determin the z-index of the rendering, every item has one the
 *          smaller ones will be rendered before the bigger ones. Default is
 *          zero, if all are zero, the rendering order is:
 *          1) "edge before node"
 *          2) "first added first rendering"
 * GROUPS:  used when save and load, remember the groups a item is in. So this
 *          field ONLY be set before save and read after load. Every visual item
 *          has one. it is used for two purposes:
 *          1) for us to group items together based on their purposes, then we
 *             can set treat them as a group and set group actions
 *          2) define renderer behaviors for each visual items, currently, there
 *             are six default renderers in basicChain. call addRenderer(...) to
 *             customize the behaviors. this func addes one more predicate to 
 *             customizedChain, which always used before basicChain to look up
 *             the correct renderer
 *
 * @author weeny
 *
 */
public class Marker implements Constants, DisplayListener{
	
	public static final String TEXT    = LABEL;         //String type
	public static final String DEPTH   = "z_depth";     //int type
	public static final String GROUPS  = "focus_groups";//HashSet<String> type
	
	
	/* the default six focus groups, for six basic renderers:
	 * line, arrow, polygon, curves, shape, and text */
	public static final String LINE_GROUP = "line_ren";
	public static final String ARRO_GROUP = "arrow_ren";
	public static final String POLY_GROUP = "polyline_ren";
	public static final String CURV_GROUP = "curve_ren";
	public static final String SHAP_GROUP = "shape_ren";
	public static final String TEXT_GROUP = "text_ren";
	
	
	protected Graph          dGraph          = null;//data graph
	protected VisualGraph    vGraph          = null;//visual graph
	protected String         group           = null;//visual graph group name
	protected Visualization  vis             = null;//visualization belonging to	
	//dummyRenderer is used for rendering any item which cannot find a renderer
	//in customizedChain or basicChain. This dummyRenderer can be replaced by,
	//for example, a shape renderer, if we choose to show all the dummy nodes
	protected Renderer       dummyRenderer   = new NullRenderer();	
	//the renderer manager, first we lookup the item in customizedChain
	//if we can find a renderer, that is it
	//otherwise, lookup the item in basicChain, if we find a renderer
	//that is it, but if we still cannot find a renderer, use dummyRenderer
	protected PredicateChain customizedChain = new PredicateChain();
	protected PredicateChain basicChain      = new PredicateChain();
	//dummyPred means that items cannot get renderer
	protected Predicate      dummyPred       = null;
	//if it not dummy, then it is a item
	protected Predicate      itemPred        = null;
	//temp point used in this class
	protected Point2D        tempPnt         = new Point2D.Double();
	
	//default node/edge schema in the data, which means, at least,  all the
	//fields will be saved to file if you choose to save this marker
	protected Schema dataNodeSchema = new Schema();
	protected Schema dataEdgeSchema = new Schema();
	//the default font type used in this marker
	public final static Font DEFAULT_FONT = FontLib.getFont("Helvetica", 10);
	//the default stroke type used in this marker
	public final static BasicStroke DEFAULT_STROKE = StrokeLib.getStroke(1);
	{
		//node fields
		dataNodeSchema.addInterpolatedColumn(POLYGON, float[].class);
		dataNodeSchema.addColumn(TEXT,       String.class);
		dataNodeSchema.addColumn(GROUPS,     HashSet.class, null);
		dataNodeSchema.addColumn(DEPTH,      int.class, 0);
		dataNodeSchema.addColumn(STROKE,     Stroke.class, DEFAULT_STROKE);
		dataNodeSchema.addColumn(FILLCOLOR,  int.class);
		dataNodeSchema.addColumn(STROKECOLOR,int.class);
		dataNodeSchema.addColumn(TEXTCOLOR,  int.class);
		dataNodeSchema.addColumn(SIZE,       double.class);
		dataNodeSchema.addColumn(X,          double.class);
		dataNodeSchema.addColumn(Y,          double.class);
		dataNodeSchema.addColumn(VISIBLE,    boolean.class, true);
		dataNodeSchema.addColumn(SHAPE,      int.class, -1);
		dataNodeSchema.addColumn(FONT,       Font.class, DEFAULT_FONT);
		//edge fields
		dataEdgeSchema.addColumn(GROUPS,     HashSet.class, null);
		dataEdgeSchema.addColumn(DEPTH,      int.class, 0);
		dataEdgeSchema.addColumn(STROKE,     Stroke.class, DEFAULT_STROKE);
		dataEdgeSchema.addColumn(FILLCOLOR,  int.class);
		dataEdgeSchema.addColumn(STROKECOLOR,int.class);
		dataEdgeSchema.addColumn(SIZE,       double.class);
		dataEdgeSchema.addColumn(VISIBLE,    boolean.class, true);
	}
	
	public Visualization getVis(){
		return vis;
	}
	//remove every item in this marker from all possible focus groups (i.e., all
	//focus groups that can be found in the visualization)
	protected void removeDataFromAllGroup(){
		Set<String> focusGroups = getFocusGroups();
		//for each focus group, remove all the graph elements
		//It is probably because we are about to load a new graph
		for (String group : focusGroups) {
			removeDataFromGroup(group);
		}
	}
	//remove every item in this marker from a specific focus group
	protected void removeDataFromGroup(String group){
		TupleSet focusSet = vis.getFocusGroup(group);
		Set<Tuple> toBeRemoved = new HashSet<Tuple>();
		for(Iterator<VisualItem> it = vis.items(group); it.hasNext();){
			VisualItem vi = it.next();
			if (! isMarker(vi)){
				//in case, some other visualItem not in the private graph
				//is added in to the focus group, leave them alone
				continue;
			}
			toBeRemoved.add(vi);
		}
		for (Tuple tuple : toBeRemoved) {
			focusSet.removeTuple(tuple);
		}
	}
	//collect all the renderers listed in the chain
	protected List<Renderer> getRenderers(){
		List<Renderer> result = Pre.getRenders(customizedChain);
		result.addAll(Pre.getRenders(basicChain));
		return result;
	}
	//collect all focus groups having renderers with them.
	protected List<String> getRendererGroups(Renderer renderer){
		List<String> groups = Pre.getFocusGroups(basicChain, renderer);
		groups.addAll(Pre.getFocusGroups(customizedChain, renderer));
		return groups;
	}
	//collect all focus groups from the visualization
	protected Set<String> getFocusGroups(){
		Set<String> groups = new HashSet<String>();
		try {//we use reflection
			Field field = Visualization.class.getDeclaredField("m_focus");
			field.setAccessible(true);
			Map m_focus = (Map) field.get(vis);
			for (Object key : m_focus.keySet()){
				groups.add(key.toString());
			}
		} catch (Exception e) {
			Debug.prob("cannot access m_focus in visualization");
		}
		return groups;
	}
	
	/**
	 * try to find a renderer for a item for this item, the result could be null
	 * if it is not controled by any renderer groups
	 */
	public Renderer getMarkerRenderer(VisualItem item){
		Renderer renderer = (Renderer) customizedChain.get(item);
		if (renderer == null) {
			renderer = (Renderer) basicChain.get(item);
		}
		return renderer;
	}
	
	//if some items are added to or removed from a rendering group, we should 
	//invalidate it, so this listener should listen to all renderering group,
	//which means it should work with addRenderer(...)
	protected TupleSetListener renGroupListener = new TupleSetListener() {
		public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] remove) {
			if (add != null) {
				for (Tuple tuple : add) {
					if (tuple instanceof VisualItem) {
						((VisualItem) tuple).setValidated(false);
					}
				}
			}
			if (remove != null){
				for (Tuple tuple : remove) {
					if (tuple instanceof VisualItem) {
						((VisualItem) tuple).setValidated(false);
					}
				}
			}
		}
	};
	
	/**
	 * Adding a renderer group, so every item in that group will be renderered
	 * by this renderer. If a item is added into different renderer groups, it
	 * works as "(group) First Added, First Check" and "(renderer) First Found,
	 * First Use"
	 */
	public void addRenderer(String group, Renderer ren){
		addRenderer(group, ren, customizedChain);
	}
	public void addRenderer(Predicate filter, Renderer ren){
		if (filter instanceof InGroupPredicate){
			int start = filter.toString().lastIndexOf("(")+2;
			int end = filter.toString().length()-2;
			String group = filter.toString().substring(start, end);
			addRenderer(group, ren, customizedChain);
		}
		else {
			Debug.prob("Predicate "+filter+" wont be remembered!!");
			Debug.prob("InGroupPredicate is better");
			customizedChain.add(filter, ren);
		}
	}
	//Adding a renderer group, called by above two public function.
	//Also used in the consructor for the basicChain
	//If we add a focus group for a renderer, we should listen to the tupleset
	//so when items are added or deleted, we need to update the listener
	protected void addRenderer(String g, Renderer ren, PredicateChain chain) {
		if (vis.getGroup(g) != null && vis.getFocusGroup(g) == null){
			Predicate filter = new InGroupPredicate(g);
			chain.add(filter, ren);
			return;
		}
		safeFocusGroup(g).addTupleSetListener(renGroupListener);
		Predicate filter = new InGroupPredicate(g);
		chain.add(filter, ren);
	}

	
	/**
	 * Add a field to the data node table, which can be saved to file
	 */
	public void addDataNodeField(String field, Class type){
		Graph graph = (Graph) vis.getSourceData(vGraph.getGroup());
		graph.getNodeTable().addColumn(field, type);
	}
	/**
	 * Add a field to the data edge table, which can be saved to file
	 */
	public void addDataEdgeField(String field, Class type){
		Graph graph = (Graph) vis.getSourceData(vGraph.getGroup());
		graph.getEdgeTable().addColumn(field, type);
	}
	
	/**
	 * Remove a field from the data node table
	 */
	public void removeDataNodeField(String field){
		dGraph.getNodeTable().removeColumn(field);
	}
	/**
	 * Remove a field from the data edge table.
	 */
	public void removeDataEdgeField(String field){
		dGraph.getEdgeTable().removeColumn(field);
	}
	/**
	 * Add a field to both node and edge table, which can be saved to file
	 */
	public void addDataField(String field, Class type){
		Graph graph = (Graph) vis.getSourceData(vGraph.getGroup());
		graph.addColumn(field, type);
	}
	/**
	 * Add a field to visual node table and edge table (will not be saved) 
	 */
	public void addVisualField(String field, Class type){
		vGraph.addColumn(field, type);
	}
	/**
	 * Add a field to visual node table (will not be saved)
	 */
	public void addVisualNodeField(String field, Class type){
		vGraph.getNodeTable().addColumn(field, type);
	}
	/**
	 * Add a field to visual edge table (will not be saved)
	 */
	public void addVisualEdgeField(String field, Class type){
		vGraph.getEdgeTable().addColumn(field, type);
	}
	
	public Edge dataEdge(int row) {
		return dGraph.getEdge(row);
	}
	public Node dataNode(int row) {
		return dGraph.getNode(row);
	}
	public EdgeItem visualEdge(int row) {
		return (EdgeItem) vGraph.getEdge(row);
	}
	public NodeItem visualNode(int row) {
		return (NodeItem) vGraph.getNode(row);
	}
	
	public Iterator<VisualItem> visualEdges(){
		return vGraph.edges();
	}
	public Iterator<VisualItem> visualNodes(){
		return vGraph.nodes();
	}
	
	/**
	 * Get visual node table of the data
	 */
	public VisualTable getVisualNodeTable(){
		return (VisualTable) vGraph.getNodeTable();
	}
	/**
	 * Get visual edge table of the data
	 */
	public VisualTable getVisualEdgeTable(){
		return (VisualTable) vGraph.getEdgeTable();
	}
	
	/**
	 * Clear everything related to this backend data
	 */
	public void clear(){
		removeDataFromAllGroup();
		dGraph.clear();
	}
	
	/**
	 * Set display depth of the item, default is 0.
	 * Smaller ones are rendered before bigger ones.
	 */
	public boolean setDepth(VisualItem item, int depth){
		if (isMarker(item)) {
			item.setInt(DEPTH, depth);
			return true;
		}
		else
			return false;
	}

	public String getGroup(){
		return group;
	}
	
	public Marker(Visualization vis, String groupName, Graph graph){
		this.vis = vis;
		group = groupName;

		Schema nodeToAdd = Pre.unique(dataNodeSchema, graph.getNodeTable());
		Schema edgeToAdd = Pre.unique(dataEdgeSchema, graph.getEdgeTable());
		graph.getNodeTable().addColumns(nodeToAdd);
		graph.getEdgeTable().addColumns(edgeToAdd);
		
		dGraph = graph;
		vGraph = vis.addGraph(group, dGraph);
		vis.setVisible(group+".nodes", null, false);
		
		Pre.cleanDuplicatedField(vGraph);
		
		//initialization of basicChain, the preference order is 
		//(first add first check):
		//text -> curve -> polygon -> arrow -> line -> shape
		//check the text group
		LabelRenderer tRenderer = new LabelRenderer();
		tRenderer.setTextField(TEXT);
		addRenderer(TEXT_GROUP, tRenderer, basicChain);
		//check the curve group
		PolygonRenderer cRenderer = new PolylineRenderer();
		cRenderer.setClosePath(false);
		cRenderer.setPolyType(POLY_TYPE_CURVE);
		addRenderer(CURV_GROUP, cRenderer, basicChain);
		//check the polygon group
		PolygonRenderer pRenderer = new PolylineRenderer();
		pRenderer.setClosePath(false);
		pRenderer.setPolyType(POLY_TYPE_LINE);
		addRenderer(POLY_GROUP, pRenderer, basicChain);
		//check the arrow group
		EdgeRenderer aRenderer = new EdgeRenderer();
		aRenderer.setEdgeType(Constants.EDGE_TYPE_LINE);
		aRenderer.setArrowType(EDGE_ARROW_FORWARD);
		addRenderer(ARRO_GROUP, aRenderer, basicChain);
		//check the line group
		EdgeRenderer lRenderer = new EdgeRenderer();
		lRenderer.setEdgeType(Constants.EDGE_TYPE_LINE);
		lRenderer.setArrowType(EDGE_ARROW_NONE);
		addRenderer(LINE_GROUP, lRenderer, basicChain);
		//check the basic shape group
		ShapeRenderer sRenderer = new ShapeRenderer(10);
		addRenderer(SHAP_GROUP, sRenderer, basicChain);
		
		//define what is dummy and what is item
		Predicate cusNull = new NullPredicate();
		((Function) cusNull).addParameter(customizedChain.getExpression());
		Predicate dftNull = new NullPredicate();
		((Function) dftNull).addParameter(basicChain.getExpression());
		dummyPred = new AndPredicate(cusNull, dftNull); 
		itemPred  = new NotPredicate(dummyPred);
		
		//if the visualization already has some displays, 
		//we change their item sorters
		for(int i = 0; i<vis.getDisplayCount(); i++){
			hasAddedDisplay(vis.getDisplay(i));
		}
		//if it supports, we listen to the visualizaiton see if more displays
		//are added, if they are added, change their item sorters too
		if(vis instanceof MoreVisualization) {
			((MoreVisualization)vis).addDisplayListener(this);
		}
		else {
			Debug.info("Please use MoreVisualization. Otherwise, the newly " +
				"added Display will have problematic ItemSorters, which " +
				"cannot handle DEPTH field in this marker correctly, so " +
				"please set the ItemSorter by yourself");
		}
		
		Set<String> focusGroups = getFocusGroups();
		for (String group : focusGroups) {
			safeFocusGroup(group);
		}
		final RendererFactory drf = vis.getRendererFactory();
		vis.setRendererFactory(new DefaultRendererFactory(){
			@Override
			public Renderer getRenderer(VisualItem item) {
				if (isMarker(item)){
					Renderer ren = getMarkerRenderer(item);
					return ren == null? dummyRenderer : ren;
				}
				else {
					return drf.getRenderer(item);
				}
			}
		});
	}
	
	public Marker(Visualization vis, String groupName){
		this(vis, groupName, new Graph(true));
	}
	public Marker(Visualization vis){
		this(vis, IOUtil.timeString());
	}
	public Marker(){
		this(new MoreVisualization());
	}
	/*
	 * all the marked items are not rendered, unless they are added to some
	 * focus group which is taken care of by a renderer.
	 * 
	 * dummy nodes are basically used to draw lines or arrows,
	 * because they have two ends but does not need to be drawn
	 */
	
	//dummy node has no focus group, no shape, no color, no nothing
	public NodeItem dummy(){
		VisualItem item = vis.getVisualItem(group, dGraph.addNode());
		item.setSize(2);
		updateColor(item, NO_COLOR, NO_COLOR, NO_COLOR);
		return (NodeItem) item;
	}
	public NodeItem dummy(double x, double y){
		NodeItem vi = dummy();
		Pre.setPos(vi, x, y);
		return vi;
	}
	public NodeItem dummy(Point2D pos){
		if (pos == null) {
			return dummy();
		}
		else {
			return dummy(pos.getX(), pos.getY());
		}
	}
	
	//all mark functions return a visual item having visual attributes, but
	//without any focus group
	public NodeItem markItem(Point2D pos, int shape, int filStk, double size){
		NodeItem dummy = dummy(pos);
		dummy.setShape(shape);
		PrefuseLib.updateDouble(dummy, SIZE, size);
		updateColor(dummy, filStk, filStk, NO_COLOR);
		return dummy;
	}
	public NodeItem markPolyon(int color, float width, float[] xyxy){
		NodeItem dummy = dummy(0, 0);
		updateColor(dummy, color, NO_COLOR, NO_COLOR);
		dummy.setStroke(StrokeLib.getStroke(width));
		dummy.set(POLYGON, xyxy);
		return dummy;
	}
	public NodeItem markText(Point2D pos, String txt, int col, double size){
		NodeItem dummy = dummy(pos);
		dummy.setString(TEXT, txt);
		PrefuseLib.updateDouble(dummy, SIZE, size);
		updateColor(dummy, NO_COLOR, NO_COLOR, col);
		return dummy;
	}
	public EdgeItem markConnection(Node n1, Node n2){
		// connect two nodes to get a edge, again, it has no renderers
		if (n1 instanceof NodeItem) {
			n1 = (Node) vis.getSourceTuple((VisualItem) n1);
		}
		if (n2 instanceof NodeItem) {
			n2 = (Node) vis.getSourceTuple((VisualItem) n2);
		}
		return (EdgeItem) vis.getVisualItem(group, dGraph.addEdge(n1, n2));
	}
	public EdgeItem markConnection(Node n1, Node n2, int color, float width){
		EdgeItem edge = markConnection(n1, n2);
		edge.setSize(width);
		updateColor(edge, color, NO_COLOR, NO_COLOR);
		return edge;
	}
	public EdgeItem markEdge(Point2D s, Point2D t, int color, float width){
		NodeItem src = dummy(s);
		NodeItem tar = dummy(t);
		EdgeItem edge = markConnection(src, tar, color, width);
		return edge;
	}
	
	public TupleSet safeFocusGroup(String group){
		if (vis.getFocusGroup(group) == null){
			Debug.info("Created a new focus group: "+group);
			vis.addFocusGroup(group);
		}
		return vis.getFocusGroup(group);
	}
	
	public void updateColor(VisualItem vi, int sCol, int fCol, int tCol){
		PrefuseLib.updateInt(vi, STROKECOLOR, sCol);
		PrefuseLib.updateInt(vi, FILLCOLOR, fCol);
		PrefuseLib.updateInt(vi, TEXTCOLOR, tCol);
	}
	
	public static final int NO_COLOR = IntColor.white(0);
	
	//drawing functions use mark functions, they also add the visual items to
	//the correct focus groups, now the visual items can be correctly rendered
	//text functions
	public VisualItem drawText(Point2D pos, String txt, int col, double size){
		VisualItem item = markText(pos, txt, col, size);
		safeFocusGroup(TEXT_GROUP).addTuple(item);
		vis.repaint();
		return item;
	}
	public VisualItem drawText(Point2D pos, String text, double size){
		return drawText(pos, text, IntColor.gray(255), size);
	}
	//line/arrow functions
	public EdgeItem drawLine(Point2D s, Point2D t, int color, float size){
		EdgeItem edge = markEdge(s, t, color, size);
		safeFocusGroup(LINE_GROUP).addTuple(edge);
		vis.repaint();
		return edge;
	}
	public EdgeItem drawArrow(Point2D s, Point2D t, int color, float size){
		EdgeItem edge = markEdge(s, t, color, size);
		edge.setFillColor(color);
		safeFocusGroup(ARRO_GROUP).addTuple(edge);
		vis.repaint();
		return edge;
	}
	public EdgeItem drawLine(Line2D l, int strokeColor, float size){
		return drawLine(l.getP1(), l.getP2(), strokeColor, size);
	}
	public EdgeItem drawArrow(Line2D l, int color, float size){
		return drawArrow(l.getP1(), l.getP2(), color, size);
	}
	public EdgeItem drawArrow(Point2D s, double len, double dirInRad, int col){
		double dx = len*Math.cos(dirInRad);
		double dy = len*Math.sin(dirInRad);
		Point2D t = new Point2D.Double(dx+s.getX(), dy+s.getY());
		return drawArrow(s, t, col, 1);
	}
	//graph functions
	public NodeItem drawNode(Point2D pos, int shape, int color, double size){
		NodeItem node = markItem(pos, shape, color, size);
		safeFocusGroup(SHAP_GROUP).addTuple(node);
		vis.repaint();
		return node;
	}
	public EdgeItem drawEdge(Node s, Node t, int col, float wid, boolean dir){
		EdgeItem edge = markConnection(s, t, col, wid);
		if (dir) {
			safeFocusGroup(ARRO_GROUP).addTuple(edge);
		}
		else{
			safeFocusGroup(LINE_GROUP).addTuple(edge);
		}
		vis.repaint();
		return edge;
	}
	//curve/polyline item functions
	public VisualItem drawCurve(float[] xyxy, int color, float width){
		float[] array = new float[xyxy.length];
		System.arraycopy(xyxy, 0, array, 0, array.length);
		VisualItem item = markPolyon(color, width, xyxy);
		safeFocusGroup(CURV_GROUP).addTuple(item);
		vis.repaint();
		return item;
	}
	public VisualItem drawPolyline(float[] xyxy, int color, float width){
		float[] array = new float[xyxy.length];
		System.arraycopy(xyxy, 0, array, 0, array.length);
		VisualItem item = markPolyon(color, width, xyxy);
		safeFocusGroup(POLY_GROUP).addTuple(item);
		vis.repaint();
		return item;
	}
	
	public boolean isMarker(VisualItem vi){
		return (vi.isInGroup(group));
	}
	public JCheckBox createShowDummyCheck(){
		final JCheckBox check = new JCheckBox("show dummy nodes ("+group+")");
		check.setSelected(!(dummyRenderer instanceof NullRenderer));
		check.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				boolean visible = dummyRenderer instanceof ShapeRenderer;
				if (check.isSelected() && !visible) {
					dummyRenderer = new ShapeRenderer();
					vis.setValue(group, dummyPred, FILLCOLOR, IntColor.GREEN);
					vis.setValue(group, dummyPred, SHAPE, SHAPE_ELLIPSE);
				}
				if (!check.isSelected() && visible) {
					dummyRenderer = new NullRenderer();
					vis.setValue(group, dummyPred, FILLCOLOR, NO_COLOR);
					vis.setValue(group, dummyPred, SHAPE, SHAPE_NONE);
				}
				vis.setValue(group, dummyPred, VALIDATED, false);
				vis.repaint();
			}
		});
		return check;
	}
	
	protected void updateValues(Predicate filter, String field, Expression exp){
		Iterator<Tuple> it = vis.items(filter);
		while (it.hasNext()) {
			Tuple tuple = it.next();
			tuple.set(field, exp.get(tuple));
		}
	}
	protected void showPossibleVisualProblemFrame(Set<String> allGroup){
		if (!(vis instanceof MoreVisualization)) {
			Debug.prob("Please use MoreVisualization");
			return;
		}
		//cast this.vis to MoreVisualization
		final MoreVisualization vis = (MoreVisualization) this.vis;
		WidgetTabbedPane fixTabs = new WidgetTabbedPane();
		JLabel globalInfoLabel = new JLabel("Global info");
		
		if (allGroup.isEmpty()) {//none of items is added to any groups
			int cnt = DataLib.count(vis.items(dummyPred), null);
			final JLabel label = new JLabel("There are "+cnt+" dummy items");
			FilterBox filterBox = new FilterBox(vis);
			filterBox.setInfoLabel(globalInfoLabel);
			filterBox.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					int cnt = DataLib.count(vis.items(dummyPred), null);
					label.setText("There are "+cnt+" dummy items");
				}
			});
			OperationBox groupOpBox = new OperationBox(vis);
			groupOpBox.addOperation(new GroupOperation(groupOpBox));
			groupOpBox.setFilter(filterBox);
			fixTabs.addWidget("Focus Groups", label);
			fixTabs.addWidget("Focus Groups", filterBox);
			fixTabs.addWidget("Focus Groups", groupOpBox);
		}
		
		if (DataLib.max(vGraph, SIZE).getDouble(SIZE)<0.0001) {
			final ExpressionBox filter = new ExpressionBox(vis, "Filter:");
			filter.resetFuncList(ExpressionBox.DOUBLE_ITEMS);
			filter.setInfoLabel(globalInfoLabel);
			ExpressionBox valueExpBox = new ExpressionBox(vis, "Size:");
			valueExpBox.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Expression exp = ((ExpressionBox) e.getSource()).getExpr();
					updateValues((Predicate) filter.getExpr(), SIZE, exp);
				}
			});
			fixTabs.addWidget("Size", filter);
			fixTabs.addWidget("Size", valueExpBox);
		}
		if (DataLib.max(vGraph, FILLCOLOR).getInt(FILLCOLOR)==-1){
			final ExpressionBox filter = new ExpressionBox(vis, "Filter:");
			filter.resetFuncList(ExpressionBox.INT_ITEMS);
			filter.setInfoLabel(globalInfoLabel);
			ExpressionBox value = new ExpressionBox(vis, "Fill color:");
			value.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Expression exp = ((ExpressionBox) e.getSource()).getExpr();
					updateValues((Predicate) filter.getExpr(), FILLCOLOR, exp);
				}
			});
			fixTabs.addWidget("Fill color", filter);
			fixTabs.addWidget("Fill color", value);
		}
		if (DataLib.max(vGraph, STROKECOLOR).getInt(STROKECOLOR)==-1){
			final ExpressionBox filter = new ExpressionBox(vis, "Filter:");
			filter.resetFuncList(ExpressionBox.INT_ITEMS);
			filter.setInfoLabel(globalInfoLabel);
			ExpressionBox value = new ExpressionBox(vis, "Stroke color:");
			value.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Expression exp = ((ExpressionBox) e.getSource()).getExpr();
					updateValues((Predicate) filter.getExpr(), STROKECOLOR, exp);
				}
			});
			fixTabs.addWidget("Stroke color", filter);
			fixTabs.addWidget("Stroke color", value);
		}
		if (DataLib.max(vGraph.nodes(), TEXTCOLOR).getInt(TEXTCOLOR)==-1){
			final ExpressionBox filter = new ExpressionBox(vis, "Node filter:");
			filter.resetFuncList(ExpressionBox.INT_ITEMS);
			filter.setInfoLabel(globalInfoLabel);
			ExpressionBox value = new ExpressionBox(vis, "Text color:");
			value.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Expression exp = ((ExpressionBox) e.getSource()).getExpr();
					updateValues((Predicate) filter.getExpr(), FILLCOLOR, exp);
					Function isNode = FunctionTable.createFunction("ISNODE");
					Predicate pred = new AndPredicate(
							(Predicate) filter.getExpr(), (Predicate) isNode);
					updateValues(pred, TEXTCOLOR, exp);
				}
			});
			fixTabs.addWidget("Text color", filter);
			fixTabs.addWidget("Text color", value);
		}
		boolean labelAllNull = true;
		Iterator<VisualItem> it = vGraph.nodes();
		while (it.hasNext()) {
			VisualItem item = it.next();
			if (item.getString(LABEL) != null){
				labelAllNull = false;
				break;
			}
		}
		if (labelAllNull){
			final ExpressionBox filter = new ExpressionBox(vis, "Filter:");
			filter.resetFuncList(ExpressionBox.STRING_ITEMS);
			filter.setInfoLabel(globalInfoLabel);
			ExpressionBox value = new ExpressionBox(vis, "Label:");
			value.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Expression exp = ((ExpressionBox) e.getSource()).getExpr();
					updateValues((Predicate) filter.getExpr(), LABEL, exp);
				}
			});
			fixTabs.addWidget("Label", filter);
			fixTabs.addWidget("Label", value);
		}
		JFrame frame = new JFrame("Problematic items or columns");
		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frame.getContentPane().setLayout(new BorderLayout());
		frame.getContentPane().add(fixTabs, BorderLayout.CENTER);
		frame.getContentPane().add(globalInfoLabel, BorderLayout.SOUTH);
		frame.setPreferredSize(new Dimension(640,480));
		frame.pack();
		frame.setVisible(true);
	}

	/**
	 * Create a control widget for setting parameters in renderers related to
	 * this marker
	 */
	public Box createRendererControl(){
		RendererBox box = new RendererBox(vis, customizedChain, basicChain);
		box.setTitle("Renderers ("+group+")");
		return box;
	}
	
	/**
	 * Directly read graph from this input stream, it skips the user interaction
	 * and do the exactly same thing what {@link #createLoadSaveBox(String)} do.
	 */
	public void loadGraph(InputStream is){
		removeDataFromAllGroup();
		new GraphLoadSaveBox("dummy", dGraph, "dummy").loadData(is);
		restoreGroupInfoToVisualization();
	}
	
	/**
	 * Directly save graph to this output stream, it skips the user interaction
	 * and do the exactly same thing what {@link #createLoadSaveBox(String)} do.
	 */
	public void saveGraph(OutputStream os){
		recordGroupInfoInTuples();
		new GraphLoadSaveBox("dummy", dGraph, "dummy").saveData(os);
	}
	
	/**
	 * Every tuple may belong to some focus groups. When we want to save the
	 * data, we call this function to put that information in the column field
	 * called {@link #GROUPS}. So next time we read the data, we can restore it.
	 * 
	 * @see #restoreGroupInfoToVisualization()
	 */
	protected void recordGroupInfoInTuples(){
		Set<String> groups = getFocusGroups();
		Iterator<VisualItem> iter = null;
		for (String string : groups) {
			for(iter = vis.items(string); iter.hasNext();){
				VisualItem vi = iter.next();
				if (!isMarker(vi))
					continue;
				Set<String> set = (Set<String>) vi.get(GROUPS);
				if (set == null) {
					set = new HashSet<String>();
					vi.set(GROUPS, set);
				}
				set.add(string);
			}
		}
	}
	
	/**
	 * Because we use focus groups to correctly render tuples, the group info
	 * for each tuple is important. So after we load the data, we call this
	 * function to add them to their focus groups.
	 * 
	 * @see #recordGroupInfoInTuples()
	 */
	protected Set<String> restoreGroupInfoToVisualization(){
		HashSet<String> allGroup = new HashSet<String>();
		//union all focus groups
		for(Iterator<VisualItem> it = vGraph.tuples(); it.hasNext();){
			VisualItem item = it.next();
			Set<String> groupSet = (HashSet<String>) item.get(GROUPS);
			if (groupSet != null)
				allGroup.addAll(groupSet);
		}
		//make sure the new focus groups exist
		for(String group : allGroup){
			safeFocusGroup(group);
		}
		//for each item, add it to its focus groups
		for(Iterator<VisualItem> it = vGraph.tuples(); it.hasNext();){
			VisualItem item = it.next();
			Set<String> groupSet = (HashSet<String>) item.get(GROUPS);
			if (groupSet == null)
				continue;
			for (String string : groupSet) {
				safeFocusGroup(string).addTuple(item);
			}
		}
		return allGroup;
	}
	/**
	 * Create a load/save interface for the data in this marker. Basically we
	 * save everything in the {@link #dGraph}. We have some predefined fields to
	 * save, but you can add new fields by {@link #addDataField(String, Class)}.
	 * How those fields are saved? Please see {@link MyGraphReader}.
	 * <p>
	 * To load a marker, we need to clear the old data from the visualization
	 * first, before we actaully load it (i.e., pressing the "Yes" button). And
	 * After the graph is loaded, we need to add them to the right focus group
	 * so that correct renderers can be chosen for the rendering tasks. Please
	 * notice that we also accept the graph data having no {@value #GROUPS}
	 * field, which means the data is saved by some other things. However, then
	 * we have no way to render them, which is not what we want. So when we find
	 * out that there is no {@value #GROUPS} field in the file (Or you hold the
	 * ALT key when pressing the "load" button!), we show up a dialog to ask
	 * users to assign each item to a focus group. Meanwhile, we also check some
	 * other visual attribute fields: [_textColor],[_label], and [_fillColor],
	 * [_strokeColor], [_size]
	 * <p>
	 * To save a marker, we need to find out how many focus groups each every
	 * one of the data belongs to, and reset the {@value #GROUPS} field based
	 * on the updated information. So when we need to load the data from the
	 * file again, the focus group information can then be correctly set.
	 */
	public GraphLoadSaveBox createLoadSaveBox(String fileName){
		GraphLoadSaveBox box = new GraphLoadSaveBox(fileName, dGraph, group);
		//before we actually load the data, we need to clear this marker, first
		box.addPreLoadAction(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				removeDataFromAllGroup();
			}
		});
		//after the graph is loaded, we need to add them to the right focus
		//group so that renderers can be correctly chosen
		box.addPostLoadAction(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				Set<String> allGroup = restoreGroupInfoToVisualization();
				//if all the focus groups are empty, it may mean that this data
				//is from some other source, for example, some general table or
				//graph, which means none of the items are going to be rendered.
				//So we force to open a dialog to ask you to setup some basic
				//visual attributes for the input data, so that the data can be
				//probably displayed. Of course, you can cancel the dialog, and
				//set those necessary visual attributes by using the item tab.
				//Also, if the ALT key is held when pressing the load button, we
				//still open it no matter what.
				//We basically check the data graph for the following columns:
				//[focus_groups], [_fillColor], [_strokeColor], [_textColor],
				//[_label], and [_size]
				if (ModUtil.isAltDown(e.getModifiers()) || allGroup.isEmpty()) {
					showPossibleVisualProblemFrame(allGroup);
				}
				vis.repaint();
			}
		});
		//before be saved, we need to update all group information, 
		//so next time we load them, they will be added correctly
		box.addPreSaveAction(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				recordGroupInfoInTuples();
			}
		});
		return box;
	}
	
	/**
	 * Create a table interface for all the edges/lines in this marker. Details
	 * about PreJTable can be found in {@link PreJTable}
	 * @return
	 */
	public PreJTable createEdgeControlTable(){
		//adding edge tables, to control their attribute values
		String[] names = 
			new String[]{VISIBLE, DEPTH, STROKECOLOR, FILLCOLOR, SIZE, STROKE};
		ColumnProjection colFilter = new NamedColumnProjection(names, true);
		PreJTable table = new PreJTable(getVisualEdgeTable(), colFilter);
		return table;
	}
	
	/**
	 * Create a table interface for all the nodes in this marker. Details about
	 * PreJTable can be found in {@link PreJTable}
	 */
	public PreJTable createNodeControlTable(){
		String[] names = 
			new String[]{VISIBLE, DEPTH, STROKECOLOR, X, Y, SIZE, SHAPE};
		ColumnProjection proj = new NamedColumnProjection(names, true);
		//we do not include all the dummies
		PreJTable table = new PreJTable(getVisualNodeTable(), itemPred, proj);
		return table;
	}
	
	//-------------------------------------------------------------------------
	//DisplayListener functions. This listener is added in the constructor of
	//Marker. Basically, we need to make dure all the display added can handle
	//the DEPTH field correctly.
	public void hasAddedDisplay(Display display) {
		//listen to the visualization, if some display is added, update its
		//item sorter
		final ItemSorter sorter = display.getItemSorter();
		display.setItemSorter(new ItemSorter(){
			@Override
			public int score(VisualItem item) {
				if (isMarker(item)) {
					return item.getInt(DEPTH)+(item.isHover()?1:0);
				}
				else {
					return sorter.score(item);
				}
			}
		});
	}
	public void willRemoveDisplay(Display display) {/*can ignore?*/}
}